package org.tigris.atlas.jmx.weblogic;

import java.util.Properties;

import javax.management.Attribute;
import javax.management.AttributeNotFoundException;
import javax.management.InstanceNotFoundException;
import javax.management.InvalidAttributeValueException;
import javax.management.MBeanException;
import javax.management.MBeanRegistrationException;
import javax.management.ReflectionException;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;

import weblogic.management.DistributedManagementException;
import weblogic.management.MBeanCreationException;
import weblogic.management.MBeanHome;
import weblogic.management.WebLogicMBean;
import weblogic.management.configuration.DeploymentMBean;
import weblogic.management.configuration.TargetMBean;

/**
 * Provides a base implementation for resource configuration classes.s
 */
public abstract class AbstractResourceConfiguation {
	
	protected static final String ATTRIBUTE_JNDI_NAME = "JNDIName";
	protected Properties properties;
	protected String mBeanName;
	private boolean encounteredMissingProperty;
	
	/**
	 * Creates a new instance for the given MBean name and properties
	 * @param mBeanName The name of the resource to configure
	 * @param properties The properties from which the resource should be configured
	 */
	public AbstractResourceConfiguation(String mBeanName, Properties properties) {
		this.properties = properties;
		this.mBeanName = mBeanName;
	}
	
	/**
	 * Get the log instance on the subclass so we can log based on the subclasses log settings 
	 * rather than those of this abstract class
	 * @return A <tt>Log</tt> instance
	 */
	protected abstract Log getLog();
	
	/**
	 * Returns the MBean type represented by this configuration class
	 * @return The MBean type whose configuration is being managed by this class
	 */
	protected abstract String getMBeanType();
	
	/**
	 * Return the name of the resource being configured
	 * @return The name of MBean that represents this resource
	 */
	protected String getMBeanName() {
		return mBeanName;
	}
		
	/**
	 * Configures this resource by performing the following operations:
	 * <ul>
	 * <li>Find the existing instance of the resource</li>
	 * <li>If the resource already exists, delete it if <tt>WeblogicConfiguration.RECREATE_EXISTING_RESOURCES</tt> is true</li>
	 * <li>Create the existing resource if it does already exist or <tt>WeblogicConfiguration.RECREATE_EXISTING_RESOURCES</tt> is true</li>
	 * <li>Set attribute on the new resource, if one is created</tt>
	 * </ul>
	 * @param home The <tt>MBeanHome</tt> of the Weblogic domain
	 * @param targetServers The servers/clusters to which the resource should be targeted
	 * @param recreateExistingResources Whether or not to recreate existing resources.
	 * @return The newly created resource
	 */
	public WebLogicMBean configure(MBeanHome home, TargetMBean[] targetServers, boolean recreateExistingResources) {
		String mBeanName = getMBeanName();
		String mBeanType = getMBeanType();
		getLog().info("Configuring " + mBeanType + " '" + mBeanName + "'...");
		WebLogicMBean mBean = getMBean(home, mBeanName, mBeanType);
		
		if (mBean != null) {
			getLog().info("\t" + mBeanType + " '" + mBeanName + "' already exists!");
			if (recreateExistingResources) {
				deleteMBean(home, mBean);
				mBean = null;
			}
		} 
		
		if ((recreateExistingResources) || (mBean == null) ) {
			getLog().info("\tCreating new resource...");
			mBean = createAdminMBean(home, mBeanName, mBeanType);
			
			setMBeanAttributes(targetServers, mBean, home);
			
			getLog().info("\tCreated.");
		}
		getLog().info("Done");
		
		return mBean;
	}
	
	/**
	 * Targers the passed MBeans to the given servers/clusters.
	 * @param targetServers The servers/clusters on which to deploy
	 * @param mBean The resource to deploy
	 */
	protected void targetToServers(TargetMBean[] targetServers, WebLogicMBean mBean) {
		targetToServers(targetServers, (DeploymentMBean)mBean);
	}

	protected void targetToServers(TargetMBean[] targetServers, DeploymentMBean mBean) {
		if (targetServers != null) {
			int numberOfServers = targetServers.length;
			for (int i = 0; i < numberOfServers; i++) {				
				try {
					if (getLog().isInfoEnabled()) {
						getLog().info("Adding target: " + targetServers[i].getName());
					}					
					mBean.addTarget(targetServers[i]);
					if (getLog().isInfoEnabled()) {
						getLog().info("Added.");
					}
					
				} catch (InvalidAttributeValueException iave) {
					getLog().error("Cannot set target '" + targetServers[i].getName() + "' on resource '" + mBean.getName() + "'!", iave);
					
				} catch (DistributedManagementException dme) {
					getLog().error("Cannot distribute resource '" + mBean.getName() + "' on target '" + targetServers[i].getName() + "'!", dme);
					
				}
			}
		} else {
			if (getLog().isDebugEnabled()) {
				
			}
		}
	}
	
	/**
	 * Sets the needed attributes on this MBean.  This includes targeting the MBean to servers/cluster as targets
	 * are really just another kind of attribute.
	 * @param targetServers The servers/clusters on which to depoy this resource
	 * @param mBean The resource on which the attributes should be set
	 * @param home The server interface on which to set the attibutes
	 */
	protected abstract void setMBeanAttributes(TargetMBean[] targetServers, WebLogicMBean mBean, MBeanHome home);
	
	/**
	 * Gets the requested MBean or returns null if it does not exist.  This makes it easier to perform lookups as
	 * one does not have to deal the <tt>InstanceNotFoundException</tt>s.  
	 * @param home The server interface on which to look for the MBean
	 * @param mBeanName The name of the MBean to find
	 * @param mBeanType The type of the MBean to find
	 * @return The found MBean or null if it does not exist
	 */
	protected WebLogicMBean getMBean(MBeanHome home, String mBeanName, String mBeanType) {
		WebLogicMBean mBean = null;
		try {
			mBean = home.getMBean(mBeanName, mBeanType);
			
		} catch (javax.management.InstanceNotFoundException infe) {
			if (getLog().isDebugEnabled()) {
				getLog().debug("Resource '" + mBeanName + "' of type '" + mBeanType + "' does not currently exist...");
			}
		}
		return mBean;
	}
	
	/**
	 * Creates a new admin MBean on the server represented by home with the passed name and JMX type.
	 * @param home The connection to the remote server on which to configure this MBean
	 * @param mBeanName The name of the new mBean
	 * @param mBeanType The JMX type of the new MBean
	 * @return The newly created MBean
	 */
	protected WebLogicMBean createAdminMBean(MBeanHome home, String mBeanName, String mBeanType) {
		WebLogicMBean mBean = null;
		try {
			mBean = home.createAdminMBean(mBeanName, mBeanType);
			
		} catch (MBeanCreationException mbce) {
			if (getLog().isErrorEnabled()) {
				getLog().error("Could not create a new MBean of type '" + mBeanType + "' with the name '" + mBeanName + "'", mbce);
			}
		}
		
		return mBean;
	}	
	
	/**
	 * Deletes the passed MBean from the server
	 * @param home The MBean home that connects to the server
	 * @param mBean The MBean to delete
	 * @return The MBean marked for deletion
	 */
	protected WebLogicMBean deleteMBean(MBeanHome home, WebLogicMBean mBean) {
		getLog().info("\tDeleting existing resource...");
		try {
			home.deleteMBean(mBean);
			
		} catch (InstanceNotFoundException infe) {
			if (getLog().isDebugEnabled()) {
				getLog().debug("Resource '" + mBean.getName() + "' of type '" + mBean.getType() + "' does not exist to be deleted...");
			}
			
		} catch (MBeanRegistrationException mbre) {
			if (getLog().isErrorEnabled()) {
				getLog().error("An exception occurred while deleting '" + mBean.getName() + "' of type '" + mBean.getType() + "'!", mbre);
			}
		}
		
		getLog().info("\tDeleted.");
		return mBean;
	}	
	
	
	/**
	 * Adds an attribute based on the name value pair passed into the method onto the mBean passed
	 * into the method.
	 * @param name The name of the attribute
	 * @param value The value of the attribute (must be the correct JMX type)
	 * @param mBean The MBean on which to add the new attribute
	 */
	protected void addAttribute(String name, Object value, WebLogicMBean mBean) {		
		try {
			Attribute a = new Attribute(name, value);
			mBean.setAttribute(a);
			
		} catch (AttributeNotFoundException anfe) {
			if (getLog().isErrorEnabled()) {
				getLog().error("Attribute '" + name + "' could not be found!", anfe);
			}
			
		} catch (InvalidAttributeValueException iave) {
			if (getLog().isErrorEnabled()) {
				getLog().error("Attribute '" + name + "' cannot be set to an invalid value '" + value.toString() + "'!", iave);
			}
			
		} catch (MBeanException mbe) {
			if (getLog().isErrorEnabled()) {
				getLog().error("Attribute '" + name + "' could not be set!", mbe);
			}
						
		} catch (ReflectionException re) {
			if (getLog().isErrorEnabled()) {
				getLog().error("Attribute '" + name + "' could not be aceessed via reflection!", re);
			}
			
		}
		if (getLog().isDebugEnabled()) {
			//mask a password so it doesn't get propogated into log files:
			if ("password".equalsIgnoreCase(name)) {
				value = "********";
			}
			
			getLog().debug("\t\tSet attribute '" + name + "' to value '" + value.toString() + "'");
			
		}
	}
	
	protected String getRequiredProperty(String parameterName) {
		return verifyRequiredParameterExists(parameterName, true);
	}
	
	protected String getOptionalProperty(String parameterName) {
		return verifyRequiredParameterExists(parameterName, false);
	}
	
	private String verifyRequiredParameterExists(String parameterName, boolean isRequired) {
		String parameterValue = properties.getProperty(parameterName);
		if ((isRequired) && (StringUtils.isBlank(parameterValue))) {
			Log log = getLog();
			if (log.isErrorEnabled()) {
				log.error("Parameter '" + parameterName + "' is required for a configuring " + getMBeanType() + 
					" '" + getMBeanName() + "'!");
			}
			
			encounteredMissingProperty = true;
			
		}
		
		return parameterValue;
	}
	
	protected boolean isMissingRequiredProperties() {
		return encounteredMissingProperty;
	}

}
